// Copyright  (c) 2011, The Outercurve Foundation. 
// The software is licensed under the New BSD License (the "License"); 
// you may not use the software except in compliance with the License. 

namespace NMTopProtocols
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Globalization;
    using System.Runtime.InteropServices;
    using System.Text;
    using System.Windows.Forms;
    using System.Windows.Forms.DataVisualization.Charting;
    using Microsoft.NetworkMonitor;
    using Microsoft.NetworkMonitor.Automation;
    using Microsoft.Win32;
    using System.Threading;
    using System.Diagnostics;

    /// <summary>
    /// The Top Protocols Tool calculates the number of bytes and frames used by
    /// each Protocol in a Network Trace.  This tool also provides the ability
    /// to view the data in a hierarchical fashion as well as a function of Time.
    /// </summary>
    public partial class TopProtocols : Form
    {
        // API HELPERS
        // ---------------------------------------------------------------------------------------

        /// <summary>
        /// NMAPI Success Constant
        /// </summary>
        private const ulong ERROR_SUCCESS = 0;

        // Our Code
        // ---------------------------------------------------------------------------------------

        /// <summary>
        /// Buffer Length for Property/Protocol Name Retrieval
        /// </summary>
        private const uint BufferLength = 256;

        /// <summary>
        /// Registry Path to store Settings
        /// </summary>
        private const string RegistryPath = @"HKEY_CURRENT_USER\Software\Microsoft\Netmon3\Experts\5A99B4EC-5D4A-4aad-A66E-DB1BB80DEE8A";

        /// <summary>
        /// Used to store the reference to the callback function for function passing
        /// </summary>
        private static ParserCallbackDelegate errorCallback = new ParserCallbackDelegate(ParserCallback);

        /// <summary>
        /// Values to Populate the Time Slices Options Menu with
        /// </summary>
        private int[] sliceValues = new int[] { 10, 25, 50, 100, 250, 500, 1000 };

        /// <summary>
        /// Values to Populate the Cutoff Percentage Options Menu with
        /// </summary>
        private int[] percentValues = new int[] { 10, 20, 30, 40, 50, 60, 70, 80, 90 };

        /// <summary>
        /// Place to store API version number
        /// </summary>
        private Version apiVersion;

        /// <summary>
        /// Used as Mutex for Overview Tree/Grid selection to prevent looping
        /// </summary>
        private bool selecting;

        /// <summary>
        /// Command Line Arguments
        /// </summary>
        private CommandLineArguments parameters;

        /// <summary>
        /// Stores the number of Time Slices being used
        /// </summary>
        private int numberOfSlices = 25;

        /// <summary>
        /// Stores the cutoff Percentage being used
        /// </summary>
        private int cutoffPercent = 10;

        /// <summary>
        /// Used to store the current location of the mouse on the Time Graph
        /// </summary>
        private Point mousePoint = new Point(0, 0);

        /// <summary>
        /// Chart Position of Mouse Point in Time Graph
        /// </summary>
        private double chartX, chartY;

        /// <summary>
        /// Selected Series on Time Graph
        /// </summary>
        private Series selectedSeries;

        /// <summary>
        /// Selected Point on Time Graph
        /// </summary>
        private DataPoint selectedPoint;

        /// <summary>
        /// Filename of currently opened File
        /// </summary>
        private string loadedFile;

        /// <summary>
        /// Keeps track of when we're loading a file, used to escape loading on close
        /// </summary>
        private bool loading;

        /// <summary>
        /// Number of Tabs Being Displayed (used for when Graph Components aren't installed)
        /// </summary>
        private int numberOfTabs = 2;

        /// <summary>
        /// Is the Time Graph Component Loaded
        /// </summary>
        private bool timegraphEnabled = true;

        /// <summary>
        /// Is the Pie Graph Component Loaded
        /// </summary>
        private bool piegraphEnabled = true;

        /// <summary>
        /// The Time Graph Component, Needed to Isolate Program from Errors when not Installed
        /// </summary>
        private System.Windows.Forms.DataVisualization.Charting.Chart timeSeriesChartControl;

        /// <summary>
        /// The Pie Chart Component, Needed to Isolate Program from Errors when not Installed
        /// </summary>
        private System.Windows.Forms.DataVisualization.Charting.Chart pieSeriesChartControl;
        
        #region Initialization
        /// <summary>
        /// Initializes a new instance of the TopProtocols class.
        /// </summary>
        /// <param name="para">Command Line Arguments Object</param>
        public TopProtocols(CommandLineArguments para)
        {
            InitializeComponent();

            try
            {
                this.CreateTimeSeriesComponent();                
            }
            catch (System.IO.IOException)
            {
                (new ChartErrorDialog()).ShowDialog();

                calculateTimeGraphToolStripMenuItem.Enabled = false;
                timeGraphOptionsToolStripMenuItem.Enabled = false;

                this.timegraphEnabled = false;
            }

            try
            {
                this.CreatePieChartComponent();
            }
            catch (System.IO.IOException)
            {
                (new ChartErrorDialog()).ShowDialog();

                /*calculateTimeGraphToolStripMenuItem.Enabled = false;
                timeGraphOptionsToolStripMenuItem.Enabled = false;*/
                this.piegraphEnabled = false;
            }

            this.parameters = para;

            this.LoadOptions();

            // Create Sub Menus
            foreach (int x in this.sliceValues)
            {
                ToolStripMenuItem item = new ToolStripMenuItem();
                item.Text = x.ToString(CultureInfo.InvariantCulture);
                item.CheckOnClick = true;
                if (x == this.numberOfSlices)
                {
                    item.Checked = true;
                }

                item.Click += new EventHandler(delegate(object sender, EventArgs e)
                {
                    foreach (ToolStripMenuItem ditem in numberOfSlicesToolStripMenuItem.DropDownItems)
                    {
                        if (ditem != item)
                        {
                            ditem.Checked = false;
                        }
                    }

                    this.numberOfSlices = int.Parse(item.Text, CultureInfo.InvariantCulture);
                });
                numberOfSlicesToolStripMenuItem.DropDownItems.Add(item);
            }

            foreach (int x in this.percentValues)
            {
                ToolStripMenuItem item = new ToolStripMenuItem();
                item.Text = x.ToString(CultureInfo.InvariantCulture);
                item.CheckOnClick = true;
                if (x == this.cutoffPercent)
                {
                    item.Checked = true;
                }

                item.Click += new EventHandler(delegate(object sender, EventArgs e)
                {
                    foreach (ToolStripMenuItem ditem in minimumCutoffPercentToolStripMenuItem.DropDownItems)
                    {
                        if (ditem != item)
                        {
                            ditem.Checked = false;
                        }
                    }

                    this.cutoffPercent = int.Parse(item.Text, CultureInfo.InvariantCulture);
                });
                minimumCutoffPercentToolStripMenuItem.DropDownItems.Add(item);
            }

            if (this.parameters.CaptureFileName != null)
            {
                reloadFileToolStripMenuItem.Enabled = true;
                this.Visible = true;
                this.LoadFile(this.parameters.CaptureFileName);
            }

            // Retrieve API version number
            ushort major;
            ushort minor;
            ushort build;
            ushort revision;
            NetmonAPI.NmGetApiVersion(out major, out minor, out build, out revision);

            this.apiVersion = new Version(major, minor, build, revision);

            try
            {
                this.richTextBoxHelp.LoadFile("HowDoIUseNmTopProtocols.rtf");
            }
            catch (System.IO.IOException)
            {
                this.richTextBoxHelp.Text = "Could Not Load Help File";
            }
        }
        #endregion

        #region Parser Helper Functions
        /// <summary>
        /// Called by NMAPI to notify us of status on Parser Engine Initialization
        /// </summary>
        /// <param name="callerContext">Status Label Context</param>
        /// <param name="statusCode">Status Code</param>
        /// <param name="description">Text Description</param>
        /// <param name="messageType">Type of Message</param>
        public static void ParserCallback(IntPtr callerContext, uint statusCode, string description, NmCallbackMsgType messageType)
        {
            GCHandle gch = GCHandle.FromIntPtr(callerContext);
            ToolStripStatusLabel ts = (ToolStripStatusLabel)gch.Target;

            if (messageType == NmCallbackMsgType.Error)
            {
                ts.Text = "ERROR: " + description;
            }
            else
            {
                ts.Text = description;
            }

            ts.Invalidate();
        }

        /// <summary>
        /// Checks the "Property.TCPContinuation" or TCPKeepAlive Flag to see if this frame is a continuation of a previous one.
        /// </summary>
        /// <param name="frameParser">Frame Parser Handle</param>
        /// <param name="propertyId">Flag Property ID</param>
        /// <returns>Returns the state of the flag</returns>
        private static bool CheckFlag(IntPtr frameParser, uint propertyId)
        {
            uint rnum = 0;
            uint errno;
            uint returnlength;
            NmPropertyValueType valuetype;
            byte[] buffer = new byte[TopProtocols.BufferLength];

            unsafe
            {
                fixed (byte* pbuf = buffer)
                {
                    errno = NetmonAPI.NmGetPropertyById(frameParser, propertyId, TopProtocols.BufferLength, pbuf, out returnlength, out valuetype, 0, null);
                }
            }

            if (errno == ERROR_SUCCESS)
            {
                switch (valuetype)
                {
                    case NmPropertyValueType.PropertyValueString:
                        rnum = uint.Parse(Encoding.Unicode.GetString(buffer, 0, (int)returnlength), CultureInfo.InvariantCulture);
                        break;
                    case NmPropertyValueType.PropertyValueSignedNumber:
                        rnum = (uint)BitConverter.ToUInt32(buffer, 0);
                        break;
                    case NmPropertyValueType.PropertyValueUnsignedNumber:
                        rnum = BitConverter.ToUInt32(buffer, 0);
                        break;
                }
            }
            else
            {
                Debug.WriteLine("Property Error " + errno);
            }

            return rnum != 0;
        }

        /// <summary>
        /// Checks for Flags.Ack && PayloadLength == 0 to see if this frame is a continuation of a previous one.
        /// </summary>
        /// <param name="frameParser">Frame Parser Handle</param>
        /// <param name="propertyId">Flag Property ID</param>
        /// <returns>Returns the state of the flag</returns>
        private static bool CheckAck(IntPtr frameParser, uint propertyFlagsId, uint propertyPayloadId)
        {
            string flags;
            uint payloadLength = 0;
            uint errno;
            uint returnlength;
            NmPropertyValueType valuetype;
            byte[] buffer = new byte[TopProtocols.BufferLength];

            unsafe
            {
                fixed (byte* pbuf = buffer)
                {
                    errno = NetmonAPI.NmGetPropertyById(frameParser, propertyFlagsId, TopProtocols.BufferLength, pbuf, out returnlength, out valuetype, 0, null);
                }
            }

            if (errno == ERROR_SUCCESS)
            {
                flags = Encoding.Unicode.GetString(buffer, 0, (int)returnlength);
            }
            else
            {
                Debug.WriteLine("Property Error " + errno);

                return false;
            }

            unsafe
            {
                fixed (byte* pbuf = buffer)
                {
                    errno = NetmonAPI.NmGetPropertyById(frameParser, propertyPayloadId, TopProtocols.BufferLength, pbuf, out returnlength, out valuetype, 0, null);
                }
            }

            if (errno == ERROR_SUCCESS)
            {
                switch (valuetype)
                {
                    case NmPropertyValueType.PropertyValueSignedNumber:
                        payloadLength = (uint)BitConverter.ToUInt32(buffer, 0);
                        break;
                    case NmPropertyValueType.PropertyValueUnsignedNumber:
                        payloadLength = BitConverter.ToUInt32(buffer, 0);
                        break;
                }
            }
            else
            {
                Debug.WriteLine("Property Error " + errno);
            }

            return flags.Contains("A") && payloadLength == 0;
        }

        /// <summary>
        /// Checks for Property.TCPRetransmit && Property.TCPNextSeqNumber &lt; Property.TCPBiggestReTransNextSeqNumberPro to see if this frame is a continuation of a previous one.
        /// </summary>
        /// <param name="frameParser">Frame Parser Handle</param>
        /// <param name="propertyId">Flag Property ID</param>
        /// <returns>Returns the state of the flag</returns>
        private static bool CheckRetransmit(IntPtr frameParser, uint propertyRetransmit, uint propertyNextSeqNumber, uint propertyBiggestSeqNumber)
        {
            if (TopProtocols.CheckFlag(frameParser, propertyRetransmit))
            {
                uint nextSeqNum = 0;
                uint biggestSeqNum = 0;
                uint errno;
                uint returnlength;
                NmPropertyValueType valuetype;
                byte[] buffer = new byte[TopProtocols.BufferLength];

                unsafe
                {
                    fixed (byte* pbuf = buffer)
                    {
                        errno = NetmonAPI.NmGetPropertyById(frameParser, propertyNextSeqNumber, TopProtocols.BufferLength, pbuf, out returnlength, out valuetype, 0, null);
                    }
                }

                if (errno == ERROR_SUCCESS)
                {
                    switch (valuetype)
                    {
                        case NmPropertyValueType.PropertyValueSignedNumber:
                            nextSeqNum = (uint)BitConverter.ToUInt32(buffer, 0);
                            break;
                        case NmPropertyValueType.PropertyValueUnsignedNumber:
                            nextSeqNum = BitConverter.ToUInt32(buffer, 0);
                            break;
                    }
                }
                else
                {
                    Debug.WriteLine("Property Error " + errno);
                }

                unsafe
                {
                    fixed (byte* pbuf = buffer)
                    {
                        errno = NetmonAPI.NmGetPropertyById(frameParser, propertyBiggestSeqNumber, TopProtocols.BufferLength, pbuf, out returnlength, out valuetype, 0, null);
                    }
                }

                if (errno == ERROR_SUCCESS)
                {
                    switch (valuetype)
                    {
                        case NmPropertyValueType.PropertyValueSignedNumber:
                            biggestSeqNum = (uint)BitConverter.ToUInt32(buffer, 0);
                            break;
                        case NmPropertyValueType.PropertyValueUnsignedNumber:
                            biggestSeqNum = BitConverter.ToUInt32(buffer, 0);
                            break;
                    }
                }
                else
                {
                    Debug.WriteLine("Property Error " + errno);
                }
           
                return nextSeqNum < biggestSeqNum;
            }

            return false;
        }

        /// <summary>
        /// Retrieves the value of the "Property.TCPContinuedFrameID" Property to get the parent packet ID number
        /// of this continues frame.
        /// </summary>
        /// <param name="frameParser">Frame Parser Handle</param>
        /// <param name="directionId">Direction Property ID</param>
        /// <param name="sequenceId">Sequence Property ID</param>
        /// <returns>The TCP Sequence Key</returns>
        private static string BuildSequenceKey(IntPtr frameParser, uint directionId, uint sequenceId)
        {
            uint errno;
            uint returnlength;
            NmPropertyValueType valuetype;
            byte[] buffer = new byte[TopProtocols.BufferLength];
            byte[] rarray = new byte[5];

            unsafe
            {
                fixed (byte* pbuf = buffer)
                {
                    errno = NetmonAPI.NmGetPropertyById(frameParser, directionId, TopProtocols.BufferLength, pbuf, out returnlength, out valuetype, 0, null);
                }
            }

            if (errno == ERROR_SUCCESS)
            {
                rarray[0] = buffer[0];
            }
            else
            {
                Debug.WriteLine("Property2 Error " + errno);
            }

            unsafe
            {
                fixed (byte* pbuf = buffer)
                {
                    errno = NetmonAPI.NmGetPropertyById(frameParser, sequenceId, TopProtocols.BufferLength, pbuf, out returnlength, out valuetype, 0, null);
                }
            }

            if (errno == ERROR_SUCCESS)
            {
                Array.Copy(buffer, 0, rarray, 1, 4);
            }
            else
            {
                Debug.WriteLine("Property3 Error " + errno);
            }

            return BitConverter.ToString(rarray);
        }

        /// <summary>
        /// Constructs a string representing the path to the protocols for the field
        /// e.g. Ethernet.IPv4.TCP.HTTP
        /// </summary>
        /// <param name="parsedFrame">Parsed Frame Handle</param>
        /// <param name="fieldCount">Number of Fields</param>
        /// <returns>The Protocol Path</returns>
        private static string BuildParentProtocolPath(IntPtr parsedFrame, uint fieldCount)
        {
            uint errno;
            string path = string.Empty;
            string lastProtocol = "Frame";

            for (uint f = 0; f < fieldCount; f++)
            {
                // Extract the Protocol Name from the First field
                char[] name = new char[TopProtocols.BufferLength];
                unsafe
                {
                    fixed (char* pstr = name)
                    {
                        errno = NetmonAPI.NmGetFieldName(parsedFrame, f, NmParsedFieldNames.ContainingProtocolName, TopProtocols.BufferLength, pstr);
                    }
                }

                if (errno == ERROR_SUCCESS)
                {
                    // Need to take the Path, but only up until the containing protocol
                    string n = new string(name, 0, (int)TopProtocols.BufferLength - 1).Replace("\0", string.Empty);
                    if (n != lastProtocol && !path.Contains(n))
                    {
                        path += "." + n;
                        lastProtocol = n;
                    }
                }
            }

            if (path.Length < 1)
            {
                return path;
            }

            // Hard-coded as "remainders" show up as "Frame.UndescribedFrameData" which leaves a blank "Frame" protocol which is meaningless
            // This filters those out and remain truer to the intent
            if (path.Contains("UndescribedFrameData") && path != "Frame.UndescribedFrameData")
            {
                path = path.Replace("Frame.UndescribedFrameData", "UndescribedFrameData");
            }

            // Strip Preceeding "."
            return path.Substring(1);
        }

        /// <summary>
        /// Given a parsed frames returns the timestamp of the frame
        /// </summary>
        /// <param name="parsedFrame">Parsed Frame Handle</param>
        /// <returns>The Frame Timestamp</returns>
        private static DateTime GetFrameTimeStamp(IntPtr parsedFrame)
        {
            ulong timestamp;
            uint errno = NetmonAPI.NmGetFrameTimeStamp(parsedFrame, out timestamp);
            if (errno == ERROR_SUCCESS)
            {
                return DateTime.FromFileTimeUtc((long)timestamp);
            }

            return new DateTime();
        }

        /// <summary>
        /// Calculates the Overall TimeSpan of the Capture File
        /// Assumes First Frame and Last Frame come First and Last in Time Sequence
        /// </summary>
        /// <param name="captureFile">Capture File Handle</param>
        /// <param name="frameCount">Number of Frames in Capture</param>
        /// <param name="frameParser">Frame Parser Handle</param>
        /// <returns>The TimeSpan between the start and end of the file</returns>
        private static TimeSpan CalculateTimeSpan(IntPtr captureFile, uint frameCount, IntPtr frameParser)
        {
            DateTime startTime = new DateTime(), endTime = new DateTime();

            // Get the Raw Frame data
            IntPtr rawFrame = IntPtr.Zero;
            uint errno = NetmonAPI.NmGetFrame(captureFile, 0, out rawFrame);
            if (errno == ERROR_SUCCESS)
            {
                // Parse the Raw Frame
                IntPtr parsedFrame = IntPtr.Zero;
                IntPtr insertedFrame = IntPtr.Zero;
                errno = NetmonAPI.NmParseFrame(frameParser, rawFrame, 0, NmFrameParsingOption.None, out parsedFrame, out insertedFrame);
                if (errno == ERROR_SUCCESS)
                {
                    startTime = TopProtocols.GetFrameTimeStamp(parsedFrame);

                    NetmonAPI.NmCloseHandle(insertedFrame);
                    NetmonAPI.NmCloseHandle(parsedFrame);

                    // Get the Raw Frame data
                    IntPtr rawFrame2 = IntPtr.Zero;
                    uint errno2 = NetmonAPI.NmGetFrame(captureFile, frameCount - 1, out rawFrame2);
                    if (errno2 == ERROR_SUCCESS)
                    {
                        // Parse the Raw Frame
                        IntPtr parsedFrame2 = IntPtr.Zero;
                        IntPtr insertedFrame2 = IntPtr.Zero;
                        errno2 = NetmonAPI.NmParseFrame(frameParser, rawFrame2, frameCount - 1, NmFrameParsingOption.None, out parsedFrame2, out insertedFrame);
                        if (errno2 == ERROR_SUCCESS)
                        {
                            endTime = TopProtocols.GetFrameTimeStamp(parsedFrame2);

                            NetmonAPI.NmCloseHandle(insertedFrame2);
                            NetmonAPI.NmCloseHandle(parsedFrame2);
                        }

                        NetmonAPI.NmCloseHandle(rawFrame2);
                    }
                }

                NetmonAPI.NmCloseHandle(rawFrame);
            }

            return endTime - startTime;
        }
        #endregion

        #region Graph Component Loading
        /// <summary>
        /// Loads Time Graph Component to allow for error handling
        /// </summary>
        private void CreateTimeSeriesComponent()
        {
            this.timeSeriesChartControl = new System.Windows.Forms.DataVisualization.Charting.Chart();

            System.Windows.Forms.DataVisualization.Charting.ChartArea chartArea1 = new System.Windows.Forms.DataVisualization.Charting.ChartArea();
            System.Windows.Forms.DataVisualization.Charting.Series series1 = new System.Windows.Forms.DataVisualization.Charting.Series();
            ((System.ComponentModel.ISupportInitialize)this.timeSeriesChartControl).BeginInit();

            // timeSeriesChartControl
            chartArea1.Name = "ChartArea1";
            this.timeSeriesChartControl.ChartAreas.Add(chartArea1);
            this.timeSeriesChartControl.ContextMenuStrip = this.contextMenuStripTimeGraph;
            this.timeSeriesChartControl.Dock = System.Windows.Forms.DockStyle.Fill;
            this.timeSeriesChartControl.Location = new System.Drawing.Point(0, 0);
            this.timeSeriesChartControl.Name = "timeSeriesChartControl";
            this.timeSeriesChartControl.Palette = System.Windows.Forms.DataVisualization.Charting.ChartColorPalette.Bright;
            series1.ChartArea = "ChartArea1";
            series1.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Spline;
            series1.Name = "Series1";
            this.timeSeriesChartControl.Series.Add(series1);
            this.timeSeriesChartControl.Size = new System.Drawing.Size(482, 383);
            this.timeSeriesChartControl.TabIndex = 0;
            this.timeSeriesChartControl.Paint += new System.Windows.Forms.PaintEventHandler(this.TimeSeriesChartControl_Paint);
            this.timeSeriesChartControl.MouseWheel += new System.Windows.Forms.MouseEventHandler(this.OnMouseWheel);
            this.timeSeriesChartControl.MouseMove += new System.Windows.Forms.MouseEventHandler(this.TimeSeriesChartControl_MouseMove);

            ((System.ComponentModel.ISupportInitialize)this.timeSeriesChartControl).EndInit();

            this.splitContainer2.Panel2.Controls.Add(this.timeSeriesChartControl);
            this.numberOfTabs++;
        }

        /// <summary>
        /// Loads Pie Chart Component to allow for error handling
        /// </summary>
        private void CreatePieChartComponent()
        {
            this.pieSeriesChartControl = new System.Windows.Forms.DataVisualization.Charting.Chart();

            System.Windows.Forms.DataVisualization.Charting.ChartArea chartArea1 = new System.Windows.Forms.DataVisualization.Charting.ChartArea();
            System.Windows.Forms.DataVisualization.Charting.Series series1 = new System.Windows.Forms.DataVisualization.Charting.Series();
            ((System.ComponentModel.ISupportInitialize)this.pieSeriesChartControl).BeginInit();

            // timeSeriesChartControl
            chartArea1.Name = "ChartArea1";
            chartArea1.Area3DStyle.Enable3D = true;
            chartArea1.Area3DStyle.Inclination = 20;
            this.pieSeriesChartControl.ChartAreas.Add(chartArea1);
            this.pieSeriesChartControl.ContextMenuStrip = this.contextMenuStripTimeGraph;
            this.pieSeriesChartControl.Dock = System.Windows.Forms.DockStyle.Fill;
            this.pieSeriesChartControl.Location = new System.Drawing.Point(0, 0);
            this.pieSeriesChartControl.Name = "pieSeriesChartControl";
            this.pieSeriesChartControl.Palette = System.Windows.Forms.DataVisualization.Charting.ChartColorPalette.Bright;
            series1.ChartArea = "ChartArea1";
            series1.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Pie;
            series1.Name = "Series1";
            this.pieSeriesChartControl.Series.Add(series1);
            this.pieSeriesChartControl.Size = new System.Drawing.Size(482, 383);
            this.pieSeriesChartControl.TabIndex = 0;
            /*this.pieSeriesChartControl.Paint += new System.Windows.Forms.PaintEventHandler(this.TimeSeriesChartControl_Paint);
            this.pieSeriesChartControl.MouseWheel += new System.Windows.Forms.MouseEventHandler(this.OnMouseWheel);
            this.pieSeriesChartControl.MouseMove += new System.Windows.Forms.MouseEventHandler(this.TimeSeriesChartControl_MouseMove);*/

            ((System.ComponentModel.ISupportInitialize)this.pieSeriesChartControl).EndInit();

            this.tabPagePieChart.Controls.Add(this.pieSeriesChartControl);
            this.numberOfTabs++;
        }
        #endregion

        #region Settings Loading and Saving
        /// <summary>
        /// Called when the form is closing, we'll write out our options here
        /// </summary>
        /// <param name="sender">method caller</param>
        /// <param name="e">event arguments</param>
        private void FrmMain_FormClosed(object sender, FormClosedEventArgs e)
        {
            // Save Option Settings
            if (saveOptionsOnExitToolStripMenuItem.Checked)
            {
                this.SaveOptions();
            }
        }

        /// <summary>
        /// Called when the form is about to close, we'll need to flag that we want to stop loading any files
        /// </summary>
        /// <param name="sender">method caller</param>
        /// <param name="e">event arguments</param>
        private void TopProtocols_FormClosing(object sender, FormClosingEventArgs e)
        {
            this.loading = false;
        }

        /// <summary>
        /// Loads any saved options from the Registry
        /// </summary>
        private void LoadOptions()
        {
            try
            {
                reassembleProtocolsToolStripMenuItem.Checked = bool.Parse((string)Registry.GetValue(RegistryPath, "Reassemble", "true"));
                calculateTimeGraphToolStripMenuItem.Checked = bool.Parse((string)Registry.GetValue(RegistryPath, "TimeGraph", "true"));
                if (!calculateTimeGraphToolStripMenuItem.Checked || !this.timegraphEnabled)
                {
                    tabControl.TabPages.Remove(tabPageTimeGraph);
                    selectInTimeGraphToolStripMenuItem.Enabled = false;
                    showFilteredOnTimeGraphToolStripMenuItem.Enabled = false;
                }

                this.numberOfSlices = (int)Registry.GetValue(RegistryPath, "TimeSlices", this.sliceValues[1]);
                this.cutoffPercent = (int)Registry.GetValue(RegistryPath, "TimeCutoff", this.percentValues[0]);
                automaticallyScaleTimeAxisToolStripMenuItem.Checked = bool.Parse((string)Registry.GetValue(RegistryPath, "TimeAutoScale", "true"));
                useConversationFilterToolStripMenuItem.Checked = bool.Parse((string)Registry.GetValue(RegistryPath, "Conversation", "false"));
                useDisplayFilterToolStripMenuItem.Checked = bool.Parse((string)Registry.GetValue(RegistryPath, "Display", "false"));
                useSelectedFramesToolStripMenuItem.Checked = bool.Parse((string)Registry.GetValue(RegistryPath, "Selected", "false"));
                filterGridOnTreeSelectionToolStripMenuItem.Checked = bool.Parse((string)Registry.GetValue(RegistryPath, "Filter", "true"));
                showTreeAsHierarchyToolStripMenuItem.Checked = bool.Parse((string)Registry.GetValue(RegistryPath, "Hierarchy", "true"));
                saveOptionsOnExitToolStripMenuItem.Checked = bool.Parse((string)Registry.GetValue(RegistryPath, "SaveOnExit", "false"));
                totalsToolStripMenuItem.Checked = bool.Parse((string)Registry.GetValue(RegistryPath, "ColumnTotals", "true"));
                bytesToolStripMenuItem.Checked = bool.Parse((string)Registry.GetValue(RegistryPath, "ColumnBytes", "true"));
                framesToolStripMenuItem.Checked = bool.Parse((string)Registry.GetValue(RegistryPath, "ColumnFrames", "true"));
                percentagesToolStripMenuItem.Checked = bool.Parse((string)Registry.GetValue(RegistryPath, "ColumnPercentages", "true"));
                rawDataToolStripMenuItem.Checked = bool.Parse((string)Registry.GetValue(RegistryPath, "ColumnRawData", "true"));

                // Ensure Columns are properly setup
                this.PercentagesToolStripMenuItem_Click(null, null);
                this.RawDataToolStripMenuItem_Click(null, null);
            }
            catch (ArgumentNullException)
            {
                this.SaveOptions();
            }
        }

        /// <summary>
        /// Saves our options to the registry
        /// </summary>
        private void SaveOptions()
        {
            Registry.SetValue(RegistryPath, "Reassemble", reassembleProtocolsToolStripMenuItem.Checked);
            Registry.SetValue(RegistryPath, "TimeGraph", calculateTimeGraphToolStripMenuItem.Checked);
            Registry.SetValue(RegistryPath, "TimeSlices", this.numberOfSlices);
            Registry.SetValue(RegistryPath, "TimeCutoff", this.cutoffPercent);
            Registry.SetValue(RegistryPath, "TimeAutoScale", automaticallyScaleTimeAxisToolStripMenuItem.Checked);
            Registry.SetValue(RegistryPath, "Hierarchy", showTreeAsHierarchyToolStripMenuItem.Checked);
            Registry.SetValue(RegistryPath, "Conversation", useConversationFilterToolStripMenuItem.Checked);
            Registry.SetValue(RegistryPath, "Display", useDisplayFilterToolStripMenuItem.Checked);
            Registry.SetValue(RegistryPath, "Selected", useSelectedFramesToolStripMenuItem.Checked);
            Registry.SetValue(RegistryPath, "Filter", filterGridOnTreeSelectionToolStripMenuItem.Checked);
            Registry.SetValue(RegistryPath, "SaveOnExit", saveOptionsOnExitToolStripMenuItem.Checked);
            Registry.SetValue(RegistryPath, "ColumnTotals", totalsToolStripMenuItem.Checked);
            Registry.SetValue(RegistryPath, "ColumnBytes", bytesToolStripMenuItem.Checked);
            Registry.SetValue(RegistryPath, "ColumnFrames", framesToolStripMenuItem.Checked);
            Registry.SetValue(RegistryPath, "ColumnPercentages", percentagesToolStripMenuItem.Checked);
            Registry.SetValue(RegistryPath, "ColumnRawData", rawDataToolStripMenuItem.Checked);
        }

        #endregion

        #region Main Loading and Parsing
        /// <summary>
        /// Loads a capture file to analyze it.
        /// </summary>
        /// <param name="filename">file to load</param>
        private void LoadFile(string filename)
        {
            // Setup Progress Displays
            toolStripStatusLabelParser.Text = "Loading Parser Engine...";
            toolStripProgressBar.Value = 0;
            toolStripProgressLabel.Text = "-";
            this.UseWaitCursor = true;
            this.loading = true;

            this.Refresh();

            // Configure Parser Engine
            ulong errno;

            // Passing in null for the path will use the default configuration as specified in the Netmon UI
            IntPtr nplParser = IntPtr.Zero;
            GCHandle gch = GCHandle.Alloc(toolStripStatusLabelParser);
            errno = NetmonAPI.NmLoadNplParser(null, NmNplParserLoadingOption.NmAppendRegisteredNplSets, errorCallback, GCHandle.ToIntPtr(gch), out nplParser);
            if (errno != ERROR_SUCCESS || !loading)
            {
                toolStripStatusLabel.Text = "Parser Engine Failed to Load";
                MessageBox.Show("Error Loading NMAPI Parsing Engine Error Number = " + errno, "NMAPI Error", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1, (MessageBoxOptions)0);
                toolStripStatusLabel.Text = "Loading Failed";
                this.UseWaitCursor = false;
                return;
            }

            this.Refresh();
 
            // Configure Frame Parser
            IntPtr frameParserConfig = IntPtr.Zero;
            errno = NetmonAPI.NmCreateFrameParserConfiguration(nplParser, errorCallback, GCHandle.ToIntPtr(gch), out frameParserConfig);
            if (errno != ERROR_SUCCESS || !loading)
            {
                NetmonAPI.NmCloseHandle(nplParser);
                gch.Free();
                toolStripStatusLabelParser.Text = "Parser Configuration Error Number = " + errno;
                toolStripStatusLabel.Text = "Loading Failed";
                this.UseWaitCursor = false;
                return;
            }

            this.Refresh();

            errno = NetmonAPI.NmConfigConversation(frameParserConfig, NmConversationConfigOption.None, true);
            if (errno != ERROR_SUCCESS || !loading)
            {
                NetmonAPI.NmCloseHandle(frameParserConfig);
                NetmonAPI.NmCloseHandle(nplParser);
                gch.Free();
                toolStripStatusLabelParser.Text = "Conversation Error Number = " + errno;
                toolStripStatusLabel.Text = "Loading Failed";

                this.UseWaitCursor = false;
                return;
            }

            // Add Properties
            uint propertyTCPContinuationId;
            errno = NetmonAPI.NmAddProperty(frameParserConfig, "Property.TCPContinuation", out propertyTCPContinuationId);
            if (errno != ERROR_SUCCESS || !loading)
            {
                NetmonAPI.NmCloseHandle(frameParserConfig);
                NetmonAPI.NmCloseHandle(nplParser);
                gch.Free();
                toolStripStatusLabelParser.Text = "Property Creation Error Number = " + errno;
                toolStripStatusLabel.Text = "Loading Failed";

                this.UseWaitCursor = false;
                return;
            }

            this.Refresh();

            uint propertyTCPKeepAliveId;
            errno = NetmonAPI.NmAddProperty(frameParserConfig, "Property.TCPKeepAlive", out propertyTCPKeepAliveId);
            if (errno != ERROR_SUCCESS || !loading)
            {
                NetmonAPI.NmCloseHandle(frameParserConfig);
                NetmonAPI.NmCloseHandle(nplParser);
                gch.Free();
                toolStripStatusLabelParser.Text = "Property Creation Error Number = " + errno;
                toolStripStatusLabel.Text = "Loading Failed";

                this.UseWaitCursor = false;
                return;
            }

            this.Refresh();

            uint propertyTCPDirectionId;
            errno = NetmonAPI.NmAddProperty(frameParserConfig, "Property.TCPDirection", out propertyTCPDirectionId);
            if (errno != ERROR_SUCCESS || !loading)
            {
                NetmonAPI.NmCloseHandle(frameParserConfig);
                NetmonAPI.NmCloseHandle(nplParser);
                gch.Free();
                toolStripStatusLabelParser.Text = "Property Creation Error Number = " + errno;
                toolStripStatusLabel.Text = "Loading Failed";

                this.UseWaitCursor = false;
                return;
            }

            this.Refresh();

            uint propertyTCPSeqNumber;
            errno = NetmonAPI.NmAddProperty(frameParserConfig, "Property.TCPSeqNumber", out propertyTCPSeqNumber);
            if (errno != ERROR_SUCCESS || !loading)
            {
                NetmonAPI.NmCloseHandle(frameParserConfig);
                NetmonAPI.NmCloseHandle(nplParser);
                gch.Free();
                toolStripStatusLabelParser.Text = "Property Creation Error Number = " + errno;
                toolStripStatusLabel.Text = "Loading Failed";

                this.UseWaitCursor = false;
                return;
            }

            this.Refresh();

            uint propertyTCPNextSeqNumberId;
            errno = NetmonAPI.NmAddProperty(frameParserConfig, "Property.TCPNextSeqNumber", out propertyTCPNextSeqNumberId);
            if (errno != ERROR_SUCCESS || !loading)
            {
                NetmonAPI.NmCloseHandle(frameParserConfig);
                NetmonAPI.NmCloseHandle(nplParser);
                gch.Free();
                toolStripStatusLabelParser.Text = "Property Creation Error Number = " + errno;
                toolStripStatusLabel.Text = "Loading Failed";

                this.UseWaitCursor = false;
                return;
            }

            this.Refresh();

            uint propertyTCPFlags;
            errno = NetmonAPI.NmAddProperty(frameParserConfig, "Property.TCPFlags", out propertyTCPFlags);
            if (errno != ERROR_SUCCESS || !loading)
            {
                NetmonAPI.NmCloseHandle(frameParserConfig);
                NetmonAPI.NmCloseHandle(nplParser);
                gch.Free();
                toolStripStatusLabelParser.Text = "Property Creation Error Number = " + errno;
                toolStripStatusLabel.Text = "Loading Failed";

                this.UseWaitCursor = false;
                return;
            }

            this.Refresh();

            uint propertyTCPPayloadLength;
            errno = NetmonAPI.NmAddProperty(frameParserConfig, "Property.TCPPayloadLength", out propertyTCPPayloadLength);
            if (errno != ERROR_SUCCESS || !loading)
            {
                NetmonAPI.NmCloseHandle(frameParserConfig);
                NetmonAPI.NmCloseHandle(nplParser);
                gch.Free();
                toolStripStatusLabelParser.Text = "Property Creation Error Number = " + errno;
                toolStripStatusLabel.Text = "Loading Failed";

                this.UseWaitCursor = false;
                return;
            }

            this.Refresh();

            uint propertyTCPSynRetransmit;
            errno = NetmonAPI.NmAddProperty(frameParserConfig, "Property.TCPSynRetransmit", out propertyTCPSynRetransmit);
            if (errno != ERROR_SUCCESS || !loading)
            {
                NetmonAPI.NmCloseHandle(frameParserConfig);
                NetmonAPI.NmCloseHandle(nplParser);
                gch.Free();
                toolStripStatusLabelParser.Text = "Property Creation Error Number = " + errno;
                toolStripStatusLabel.Text = "Loading Failed";

                this.UseWaitCursor = false;
                return;
            }

            this.Refresh();

            uint propertyTCPRetransmitId;
            errno = NetmonAPI.NmAddProperty(frameParserConfig, "Property.TCPRetransmit", out propertyTCPRetransmitId);
            if (errno != ERROR_SUCCESS || !loading)
            {
                NetmonAPI.NmCloseHandle(frameParserConfig);
                NetmonAPI.NmCloseHandle(nplParser);
                gch.Free();
                toolStripStatusLabelParser.Text = "Property Creation Error Number = " + errno;
                toolStripStatusLabel.Text = "Loading Failed";

                this.UseWaitCursor = false;
                return;
            }

            this.Refresh();

            uint propertyTCPBiggestReTransNextSeqNumberProId;
            errno = NetmonAPI.NmAddProperty(frameParserConfig, "Property.TCPBiggestReTransNextSeqNumberPro", out propertyTCPBiggestReTransNextSeqNumberProId);
            if (errno != ERROR_SUCCESS || !loading)
            {
                NetmonAPI.NmCloseHandle(frameParserConfig);
                NetmonAPI.NmCloseHandle(nplParser);
                gch.Free();
                toolStripStatusLabelParser.Text = "Property Creation Error Number = " + errno;
                toolStripStatusLabel.Text = "Loading Failed";

                this.UseWaitCursor = false;
                return;
            }

            this.Refresh();

            uint conversationFilterId = 0;
            if (useConversationFilterToolStripMenuItem.Checked && !string.IsNullOrEmpty(this.parameters.ConversationFilter))
            {
                errno = NetmonAPI.NmAddFilter(frameParserConfig, this.parameters.ConversationFilter, out conversationFilterId);
                if (errno != ERROR_SUCCESS || !loading)
                {
                    NetmonAPI.NmCloseHandle(frameParserConfig);
                    NetmonAPI.NmCloseHandle(nplParser);
                    gch.Free();
                    toolStripStatusLabelParser.Text = "Filter Creation Error Number = " + errno;
                    toolStripStatusLabel.Text = "Loading Failed";

                    this.UseWaitCursor = false;
                    return;
                }

                this.Refresh();
            }

            uint displayFilterId = 0;
            if (useDisplayFilterToolStripMenuItem.Checked && !string.IsNullOrEmpty(this.parameters.DisplayFilter))
            {
                errno = NetmonAPI.NmAddFilter(frameParserConfig, this.parameters.DisplayFilter, out displayFilterId);
                if (errno != ERROR_SUCCESS || !loading)
                {
                    NetmonAPI.NmCloseHandle(frameParserConfig);
                    NetmonAPI.NmCloseHandle(nplParser);
                    gch.Free();
                    toolStripStatusLabelParser.Text = "Filter Creation Error Number = " + errno;
                    toolStripStatusLabel.Text = "Loading Failed";

                    this.UseWaitCursor = false;
                    return;
                }

                this.Refresh();
            }

            IntPtr frameParser = IntPtr.Zero;
            errno = NetmonAPI.NmCreateFrameParser(frameParserConfig, out frameParser, NmFrameParserOptimizeOption.ParserOptimizeNone);
            if (errno != ERROR_SUCCESS || !loading)
            {
                NetmonAPI.NmCloseHandle(frameParserConfig);
                NetmonAPI.NmCloseHandle(nplParser);
                gch.Free();
                toolStripStatusLabelParser.Text = "Parser Creation Error Number = " + errno;
                toolStripStatusLabel.Text = "Loading Failed";
                this.UseWaitCursor = false;
                return;
            }            

            // Open Capture File
            IntPtr captureFile = IntPtr.Zero;
            errno = NetmonAPI.NmOpenCaptureFile(filename, out captureFile);
            if (errno != ERROR_SUCCESS || !loading)
            {
                NetmonAPI.NmCloseHandle(frameParser);
                NetmonAPI.NmCloseHandle(frameParserConfig);
                NetmonAPI.NmCloseHandle(nplParser);
                gch.Free();
                toolStripStatusLabelParser.Text = "Parser Engine Successfully Unloaded";

                MessageBox.Show("Could not open file: " + filename + "\n\nError Code: " + errno, "Error Loading File", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1, (MessageBoxOptions)0);
                toolStripStatusLabel.Text = "Loading Failed";
                this.UseWaitCursor = false;
                return;
            }

            // Retrieve the number of frames in this capture file
            uint frameCount;
            errno = NetmonAPI.NmGetFrameCount(captureFile, out frameCount);
            if (errno != ERROR_SUCCESS || !loading)
            {
                // Close Capture File to Cleanup
                NetmonAPI.NmCloseHandle(frameParserConfig);
                NetmonAPI.NmCloseHandle(nplParser);
                NetmonAPI.NmCloseHandle(captureFile);
                gch.Free();
                toolStripStatusLabelParser.Text = "Parser Engine Successfully Unloaded";

                MessageBox.Show("Error Retrieving Capture File Length: " + errno, "Error Reading File", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1, (MessageBoxOptions)0);
                toolStripStatusLabel.Text = "Loading Failed";

                this.UseWaitCursor = false;
                return;
            }

            // Change Window Title
            this.loadedFile = filename;
            this.Text = "Top Protocols - " + this.loadedFile;

            // Setup Progress Displays
            toolStripStatusLabelParser.Text = "Parsing Engine Loaded Successfully";

            this.Refresh();

            this.toolStripProgressBar.Value = 0;
            this.toolStripProgressBar.Maximum = (int) frameCount - 1;

            ProtocolTree tree = new ProtocolTree("Frame");
            ulong totalBytes = 0;

            System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
            timer.Interval = 5000;
            int timeelapsed = 0;
            timer.Tick += new EventHandler(delegate(object s, EventArgs args)
                {
                    if (toolStripProgressBar.Value != frameCount && loading)
                    {
                        timeelapsed += 5;
                        double timeremaining = timeelapsed * (frameCount - toolStripProgressBar.Value) / ((double)toolStripProgressBar.Value);

                        toolStripProgressLabel.Text = "Remaining: " + (int)((timeremaining / 60) % 60) + ":" + ((int)(timeremaining % 60)).ToString("00", CultureInfo.InvariantCulture);
                    }
                });
            timer.Start();

            // Used to keep track of reassembly logic
            Dictionary<string, uint> TcpContinueFrameIDByNextSeqTable = new Dictionary<string, uint>();

            // Used to store if a particular frame is made up of more than one fragment (reassembly)
            Dictionary<uint, ProtocolTree> framemap = new Dictionary<uint, ProtocolTree>();
            bool notadded;
            // Frame Number for "Parent" of continued fragment
            uint TCPContinuedFrameID;

            // Get Timespan based on first and last frames
            TimeSpan timespan = TopProtocols.CalculateTimeSpan(captureFile, frameCount, frameParser);
            double interval = timespan.Ticks / (double)this.numberOfSlices;
            
            // Ensure a small file with no timespan doesn't cause issues.
            if (interval < 0.1)
            {
                interval = 0.1;
            }

            DateTime starttime = new DateTime();

            ProtocolTree[] timesliced = new ProtocolTree[this.numberOfSlices];
            for (uint x = 0; x < this.numberOfSlices; x++)
            {
                timesliced[x] = new ProtocolTree("Frame");
            }

            // Loop through capture file
            for (uint fnum = 0; fnum < frameCount && loading; fnum++)
            {
                if (!useSelectedFramesToolStripMenuItem.Checked || (useSelectedFramesToolStripMenuItem.Checked && this.parameters.IsSelectingFrames && this.parameters.IsSelected(fnum)))
                {
                    // Get the Raw Frame data
                    IntPtr rawFrame = IntPtr.Zero;
                    errno = NetmonAPI.NmGetFrame(captureFile, fnum, out rawFrame);
                    if (errno != ERROR_SUCCESS)
                    {
                        toolStripStatusLabel.Text = "Error Retrieving Frame #" + (fnum + 1) + " from file";
                        continue;
                    }

                    // Get the length of the frame
                    uint rawFrameLength;
                    errno = NetmonAPI.NmGetRawFrameLength(rawFrame, out rawFrameLength);
                    if (errno != ERROR_SUCCESS)
                    {
                        NetmonAPI.NmCloseHandle(rawFrame);

                        toolStripStatusLabel.Text = "Error Reading Frame #" + (fnum + 1) + " from file";
                        continue;
                    }

                    // Parse the Raw Frame
                    IntPtr parsedFrame = IntPtr.Zero;
                    IntPtr insertedFrame = IntPtr.Zero;
                    errno = NetmonAPI.NmParseFrame(frameParser, rawFrame, fnum, NmFrameParsingOption.ContainingProtocolNameRequired, out parsedFrame, out insertedFrame);
                    if (errno != ERROR_SUCCESS)
                    {
                        NetmonAPI.NmCloseHandle(rawFrame);
                        toolStripStatusLabel.Text = "Error Parsing Frame #" + (fnum + 1) + " from file";
                        continue;
                    }

                    // Check against Filters
                    bool passedfilter = false;
                    if (useConversationFilterToolStripMenuItem.Checked && !string.IsNullOrEmpty(this.parameters.ConversationFilter))
                    {
                        bool passed;
                        errno = NetmonAPI.NmEvaluateFilter(parsedFrame, conversationFilterId, out passed);
                        if (errno == ERROR_SUCCESS)
                        {
                            if (passed)
                            {
                                if (useDisplayFilterToolStripMenuItem.Checked && !string.IsNullOrEmpty(this.parameters.DisplayFilter))
                                {
                                    bool passed2;
                                    errno = NetmonAPI.NmEvaluateFilter(parsedFrame, displayFilterId, out passed2);
                                    if (errno == ERROR_SUCCESS)
                                    {
                                        passedfilter = passed2;
                                    }
                                }
                                else
                                {
                                    passedfilter = true;
                                }
                            }
                        }
                    }
                    else if (useDisplayFilterToolStripMenuItem.Checked && !string.IsNullOrEmpty(this.parameters.DisplayFilter))
                    {
                        bool passed;
                        errno = NetmonAPI.NmEvaluateFilter(parsedFrame, displayFilterId, out passed);
                        if (errno == ERROR_SUCCESS)
                        {
                            passedfilter = passed;
                        }
                    }
                    else
                    {
                        passedfilter = true;
                    }

                    // Set initial time of Time Graph based on First frame, regardless of if it's in the filter
                    if (calculateTimeGraphToolStripMenuItem.Checked)
                    {
                        if (starttime == new DateTime())
                        {
                            starttime = TopProtocols.GetFrameTimeStamp(parsedFrame);
                        }
                    }

                    if (passedfilter)
                    {
                        uint fieldCount = 0;
                        errno = NetmonAPI.NmGetFieldCount(parsedFrame, out fieldCount);
                        if (errno != ERROR_SUCCESS || !loading)
                        {
                            NetmonAPI.NmCloseHandle(insertedFrame);
                            NetmonAPI.NmCloseHandle(parsedFrame);
                            NetmonAPI.NmCloseHandle(rawFrame);
                            toolStripStatusLabel.Text = "Error Retrieving Field Number for Frame #" + (fnum + 1);
                            continue;
                        }

                        // Check if this is a continuation
                        string path = string.Empty;

                        notadded = true;
                        if (reassembleProtocolsToolStripMenuItem.Checked)
                        {
                            // Generate Sequence Number based on Direction
                            string key = TopProtocols.BuildSequenceKey(frameParser, propertyTCPDirectionId, propertyTCPSeqNumber);
                            // See if we know that this is a continuation of an existing frame
                            if (TcpContinueFrameIDByNextSeqTable.ContainsKey(key))
                            {
                                TCPContinuedFrameID = TcpContinueFrameIDByNextSeqTable[key];
                            }
                            else
                            {
                                TCPContinuedFrameID = 0;
                            }

                            // Find the next Sequence Number
                            string nextkey = TopProtocols.BuildSequenceKey(frameParser, propertyTCPDirectionId, propertyTCPNextSeqNumberId);
                            bool TCPContinuation = TopProtocols.CheckFlag(frameParser, propertyTCPContinuationId);

                            // Do not change the continued frame number if it is a pure ack or keep-alive, or retransmit.
                            if (TopProtocols.CheckFlag(frameParser, propertyTCPKeepAliveId) ||
                                    TopProtocols.CheckAck(frameParser, propertyTCPFlags, propertyTCPPayloadLength) ||
                                    TopProtocols.CheckFlag(frameParser, propertyTCPSynRetransmit) ||
                                    TopProtocols.CheckRetransmit(frameParser, propertyTCPRetransmitId, propertyTCPNextSeqNumberId, propertyTCPBiggestReTransNextSeqNumberProId)
                                    )
                            {
                                // If one doesn't exist, it's zero, otherwise the same
                                if (!TcpContinueFrameIDByNextSeqTable.ContainsKey(nextkey))
                                {
                                    TcpContinueFrameIDByNextSeqTable[nextkey] = 0;
                                }
                            }
                            else
                            {
                                TcpContinueFrameIDByNextSeqTable[nextkey] = (TCPContinuation ? TCPContinuedFrameID : (fnum + 1));
                            }

                            if (TCPContinuation && TCPContinuedFrameID != 0)
                            {
                                notadded = false;

                                path = framemap[TCPContinuedFrameID - 1].Path;
                                framemap[TCPContinuedFrameID - 1].AddReassembledFrame(rawFrameLength);
                                framemap[fnum] = framemap[TCPContinuedFrameID - 1];
                            }
                            else if (TCPContinuation)
                            {
                                Debug.WriteLine("Didn't Reassemble Frame: " + (fnum + 1));
                            }
                        }

                        if (notadded)
                        {
                            path = TopProtocols.BuildParentProtocolPath(parsedFrame, fieldCount);
                            if (!showTreeAsHierarchyToolStripMenuItem.Checked)
                            {
                                // Chop Path if not doing hierarchy.
                                path = path.Substring(path.LastIndexOf(".", StringComparison.Ordinal) + 1);
                            }

                            framemap[fnum] = tree.AddProtocol(path, rawFrameLength);
                        }

                        if (calculateTimeGraphToolStripMenuItem.Checked)
                        {
                            timesliced[(int)Math.Max(0, Math.Min(this.numberOfSlices - 1, Math.Floor((double)((TopProtocols.GetFrameTimeStamp(parsedFrame) - starttime).Ticks / interval))))].AddProtocol(path.Replace("Frame.", string.Empty), rawFrameLength);
                        }

                        // Add to total bytes found
                        totalBytes += rawFrameLength;
                    }

                    NetmonAPI.NmCloseHandle(insertedFrame);
                    NetmonAPI.NmCloseHandle(parsedFrame);
                    NetmonAPI.NmCloseHandle(rawFrame);
                }

                if (fnum % 100 == 0)
                {
                    toolStripProgressBar.Value = (int)fnum;
                    toolStripStatusLabel.Text = "Loading Frame " + fnum + " of " + frameCount;
                }

                Application.DoEvents();
            }
            
            timer.Stop();

            // Close Capture File
            NetmonAPI.NmCloseHandle(captureFile);

            // Close Parser Engine
            NetmonAPI.NmCloseHandle(frameParser);
            NetmonAPI.NmCloseHandle(frameParserConfig);
            NetmonAPI.NmCloseHandle(nplParser);
            gch.Free();
                        
            if (loading)
            {
                toolStripStatusLabelParser.Text = "Parser Engine Successfully Unloaded";
                toolStripStatusLabel.Text = "Creating View...";

                // Fill in tree
                TUGrid.UseWaitCursor = true;

                TPTree.Nodes.Clear();
                TUGrid.Rows.Clear();

                uint id = 0;
                this.FillStats(frameCount, tree.GetLargestChildBytes().Bytes, totalBytes, tree, 0, null, ref id);

                // Used to create the Time Graph
                if (this.timegraphEnabled && calculateTimeGraphToolStripMenuItem.Checked)
                {
                    TimeTree.Nodes.Clear();
                    TimeTree.Nodes.Add((TreeNode)TPTree.Nodes[0].Clone());
                    TimeTree.ExpandAll();

                    this.GenerateTimeGraph(tree, timespan, timesliced, totalBytes);
                }

                if (this.piegraphEnabled)
                {
                    this.GeneratePieGraph(tree, totalBytes);
                }

                TPTree.ExpandAll();
                TPTree.SelectedNode = TPTree.Nodes[0];

                //Size gridSize = TUGrid.Size;

                //this.Size = gridSize;

                TUGrid.UseWaitCursor = false;

                // Finish with Progress Displays
                this.UseWaitCursor = false;
                toolStripProgressLabel.Text = "Loaded in " + (int)((timeelapsed / 60) % 60) + ":" + ((int)(timeelapsed % 60)).ToString("00", CultureInfo.InvariantCulture);
                toolStripProgressBar.Value = toolStripProgressBar.Maximum;
                toolStripStatusLabel.Text = "File Loaded Successfully";
            }
            loading = false;
        }

        #region Statistical & Visual Calculations       
        /// <summary>
        /// Used to fill in the tree and grid in the UI with the data obtained from parsing
        /// Relies on data placed in the Protocol Tree object
        /// </summary>
        /// <param name="frames">Number of Frames</param>
        /// <param name="largestchunk">Largest Frame in Bytes</param>
        /// <param name="bytes">Total Bytes of Capture</param>
        /// <param name="curProt">Protocol Tree</param>
        /// <param name="indent">Indentation Level</param>
        /// <param name="parentNode">Parent Node in Tree Control</param>
        /// <param name="id">Incremental ID for Row Number</param>
        private void FillStats(uint frames, ulong largestchunk, ulong bytes, ProtocolTree curProt, uint indent, TreeNode parentNode, ref uint id)
        {
            id++;
            this.FillRow(curProt, bytes, frames, indent, id);

            TreeNode node = new TreeNode(curProt.ProtocolName);
            node.Tag = curProt.ID; // Tag with unique identifier

            if (indent == 0)
            {
                TPTree.Nodes.Add(node);
            }
            else
            {
                parentNode.Nodes.Add(node);
            }

            foreach (ProtocolTree cur in curProt.Children)
            {
                this.FillStats(frames, largestchunk, bytes, cur, indent + 1, node, ref id);
            }
        }

        /// <summary>
        /// Creates a Row in the Overview Grid Control with Statistics about the capture
        /// </summary>
        /// <param name="curProt">Protocol Tree</param>
        /// <param name="bytes">Number of bytes for Protocol</param>
        /// <param name="frames">Number of frames for Protocol</param>
        /// <param name="indent">Indent Level</param>
        /// <param name="id">Row ID Number</param>
        /// <returns>The Total Percentage of Bytes</returns>
        private double FillRow(ProtocolTree curProt, ulong bytes, uint frames, uint indent, uint id)
        {
            string indent_str = string.Empty;
            for (uint i = 0; i < indent; i++)
            {
                indent_str = indent_str + " ";
            }

            // Calculate Percentages
            double bytePerc = ((double)curProt.Bytes) / bytes;
            double framesPerc = ((double)curProt.Frames) / frames;
            double reassemblyPerc = 0;
            if (curProt.Bytes != 0)
            {
                reassemblyPerc = ((double)curProt.ReassembledBytes) / curProt.Bytes;
            }

            double reassemblyFramesPerc = 0;
            if (curProt.Frames != 0)
            {
                reassemblyFramesPerc = ((double)curProt.ReassembledCount) / curProt.Frames;
            }

            ulong totBytes = curProt.GetBytesWithAllChildren();
            double totBytesPerc = ((double)totBytes) / bytes;
            if (bytes == 0)
            {
                totBytesPerc = 0;
            }

            ulong totFrames = curProt.GetFramesWithAllChildren();
            double totFramesPerc = ((double)totFrames) / frames;
            if (frames == 0)
            {
                totFramesPerc = 0;
            }

            object[] row =
                   {
                   indent_str + curProt.ProtocolName,
                   totBytes,
                   String.Format(CultureInfo.InvariantCulture, "{0,8:P02}", totBytesPerc),
                   totFrames,
                   String.Format(CultureInfo.InvariantCulture, "{0,8:P02}", totFramesPerc),
                   curProt.Bytes, 
                   String.Format(CultureInfo.InvariantCulture, "{0,8:P02}", bytePerc),
                   curProt.Frames, 
                   String.Format(CultureInfo.InvariantCulture, "{0,8:P02}", framesPerc),
                   curProt.ReassembledBytes, 
                   String.Format(CultureInfo.InvariantCulture, "{0,8:P02}", reassemblyPerc),
                   curProt.ReassembledCount, 
                   String.Format(CultureInfo.InvariantCulture, "{0,8:P02}", reassemblyFramesPerc),
                   id,
                   curProt.ID
                   };
            TUGrid.Rows.Add(row);

            return totBytesPerc;
        }

        /// <summary>
        /// Generates the Static Time Graph Series based on the loaded data
        /// </summary>
        /// <param name="tree">Protocol Tree</param>
        /// <param name="timespan">Time Span for each interval</param>
        /// <param name="timesliced">Time Sliced Data set</param>
        /// <param name="totalBytes">Total number of Bytes</param>
        private void GenerateTimeGraph(ProtocolTree tree, TimeSpan timespan, ProtocolTree[] timesliced, ulong totalBytes)
        {
            // Get all Leaves from Tree
            List<ProtocolTree> flattree = tree.GetFlatTree();

            this.timeSeriesChartControl.Series.Clear();

            ulong maxbytes = 0;

            Random rnd = new Random();

            double ms = timespan.TotalSeconds / (double)this.numberOfSlices;
            if (ms < 0.01)
            {
                ms = 0.01;
            }

            foreach (ProtocolTree leaf in flattree)
            {
                ulong tbytes = tree.GetChildBytes(leaf.Path);

                if (tbytes > 0)
                {
                    TreeNode n = this.BranchSelect(TimeTree.Nodes, leaf.ID);

                    // add the line
                    Series lineSeries = new Series();
                    lineSeries.ChartType = SeriesChartType.Line;
                    lineSeries.Color = Color.FromArgb(rnd.Next(200), rnd.Next(200), rnd.Next(200));
                    n.BackColor = lineSeries.Color;
                    if (lineSeries.Color.GetBrightness() < 0.5)
                    {
                        n.ForeColor = Color.White;
                    }

                    lineSeries.BorderWidth = 2;
                    lineSeries.Enabled = true;
                    lineSeries.Name = leaf.ID.ToString(CultureInfo.InvariantCulture);
                    lineSeries.Tag = leaf.ProtocolName;

                    lineSeries.Points.AddXY(0, 0);

                    for (int x = 1; x <= this.numberOfSlices; x++)
                    {
                        ulong bytes = timesliced[x - 1].GetChildBytes(leaf.Path);

                        if (bytes > maxbytes)
                        {
                            maxbytes = bytes;
                        }

                        lineSeries.Points.AddXY(Math.Round(x * ms, 2), bytes);
                    }

                    // Check if we've generated more traffic than the minimum cutoff
                    // If not, we'll hide this line
                    if (tbytes <= ((this.cutoffPercent / (double)100) * totalBytes))
                    {
                        lineSeries.Enabled = false;
                    }

                    n.Checked = lineSeries.Enabled;

                    this.timeSeriesChartControl.Series.Add(lineSeries);
                }
            }

            this.timeSeriesChartControl.ChartAreas[0].AxisX.Minimum = 0;
            this.timeSeriesChartControl.ChartAreas[0].AxisX.Maximum = this.numberOfSlices * ms;
            this.timeSeriesChartControl.ChartAreas[0].AxisX.Title = "Time (s)";
            this.timeSeriesChartControl.ChartAreas[0].AxisY.Minimum = 0;
            this.timeSeriesChartControl.ChartAreas[0].AxisY.Maximum = maxbytes;
            this.timeSeriesChartControl.ChartAreas[0].AxisY.Title = "Bytes";

            this.timeSeriesChartControl.Refresh();
        }

        /// <summary>
        /// Generates the Static Time Graph Series based on the loaded data
        /// </summary>
        /// <param name="tree">Protocol Tree</param>
        /// <param name="timespan">Time Span for each interval</param>
        /// <param name="timesliced">Time Sliced Data set</param>
        /// <param name="totalBytes">Total number of Bytes</param>
        private void GeneratePieGraph(ProtocolTree tree, ulong totalBytes)
        {
            // Get all Leaves from Tree
            List<ProtocolTree> flattree = tree.GetFlatTree();

            this.pieSeriesChartControl.Series.Clear();

            ulong maxbytes = 0;

            Random rnd = new Random();

            Series pieSeries = new Series();
            pieSeries.ChartType = SeriesChartType.Pie;

            foreach (ProtocolTree leaf in flattree)
            {
                ulong tbytes = tree.GetChildBytes(leaf.Path);
                
                if (tbytes > 0)
                {
                    pieSeries.Points.AddXY(leaf.ProtocolName, tbytes);
                }
            }

            this.pieSeriesChartControl.Series.Add(pieSeries);

            this.pieSeriesChartControl.Refresh();
        }
        #endregion
        #endregion

        /// <summary>
        /// Displays an About Box for the Tool
        /// </summary>
        /// <param name="sender">Message Sender</param>
        /// <param name="e">Event Arguments</param>
        private void AboutToolStripMenuItem_Click(object sender, EventArgs e)
        {
            MessageBox.Show(
                "Top Protocols v" + Application.ProductVersion + "\n" +
                "By: Michael A. Hawker\n" +
                "\n" +
                "Copyright  (c) 2011, The Outercurve Foundation.\n" +
                "This software is released under the New BSD License.\n" +
                "http://NmTopProtocols.codeplex.com/\n" +
                "\n" +
                "NMAPI Version: " + this.apiVersion + "\n" +
                "Parser Version Tested: 3.4.2350.1",
                "About Top Protocols v" + Application.ProductVersion,
                MessageBoxButtons.OK,
                MessageBoxIcon.Information,
                MessageBoxDefaultButton.Button1,
                (MessageBoxOptions)0);
        }

        /// <summary>
        /// Displays Help Tab
        /// </summary>
        /// <param name="sender">Message Sender</param>
        /// <param name="e">Event Arguments</param>
        private void GeneralHelpToolStripMenuItem_Click(object sender, EventArgs e)
        {
            tabControl.SelectTab(tabPageHelp);
        }

        #region File Menu
        /// <summary>
        /// Opens a Capture File in the Tool
        /// </summary>
        /// <param name="sender">Message Sender</param>
        /// <param name="e">Event Arguments</param>
        private void OpenCaptureFileToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (openFileDialog.ShowDialog() == DialogResult.OK)
            {
                // Revert all settings
                this.parameters = new CommandLineArguments();

                this.LoadFile(openFileDialog.FileName);

                reloadFileToolStripMenuItem.Enabled = true;
                this.loadedFile = openFileDialog.FileName;
            }
        }

        /// <summary>
        /// Reloads the current Capture File
        /// </summary>
        /// <param name="sender">Message Sender</param>
        /// <param name="e">Event Arguments</param>
        private void ReloadFileToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (this.loadedFile != null)
            {
                this.LoadFile(this.loadedFile);
            }
            else
            {
                this.LoadFile(this.parameters.CaptureFileName);
            }
        }

        /// <summary>
        /// Exits the Tool
        /// </summary>
        /// <param name="sender">Message Sender</param>
        /// <param name="e">Event Arguments</param>
        private void ExitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.Close();
        }
        #endregion

        #region Column Filter Options
        /// <summary>
        /// Show Totals in Overview Columns
        /// </summary>
        /// <param name="sender">Message Sender</param>
        /// <param name="e">Event Arguments</param>
        private void TotalsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            TotalBytes.Visible = totalsToolStripMenuItem.Checked & bytesToolStripMenuItem.Checked & rawDataToolStripMenuItem.Checked;
            TotalBytesPercent.Visible = totalsToolStripMenuItem.Checked & bytesToolStripMenuItem.Checked & percentagesToolStripMenuItem.Checked;
            TotalFrames.Visible = totalsToolStripMenuItem.Checked & framesToolStripMenuItem.Checked & rawDataToolStripMenuItem.Checked;
            TotalFramesPercent.Visible = totalsToolStripMenuItem.Checked & framesToolStripMenuItem.Checked & percentagesToolStripMenuItem.Checked;
        }

        /// <summary>
        /// Show Bytes in Overview Columns
        /// </summary>
        /// <param name="sender">Message Sender</param>
        /// <param name="e">Event Arguments</param>
        private void BytesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            TotalBytes.Visible = totalsToolStripMenuItem.Checked & bytesToolStripMenuItem.Checked & rawDataToolStripMenuItem.Checked;
            TotalBytesPercent.Visible = totalsToolStripMenuItem.Checked & bytesToolStripMenuItem.Checked & percentagesToolStripMenuItem.Checked;
            Bytes.Visible = bytesToolStripMenuItem.Checked & rawDataToolStripMenuItem.Checked;
            BytesPercent.Visible = bytesToolStripMenuItem.Checked & percentagesToolStripMenuItem.Checked;
            ReassembledBytes.Visible = bytesToolStripMenuItem.Checked & rawDataToolStripMenuItem.Checked & reassembleProtocolsToolStripMenuItem.Checked;
            ReassembledBytesPercent.Visible = bytesToolStripMenuItem.Checked & percentagesToolStripMenuItem.Checked & reassembleProtocolsToolStripMenuItem.Checked;
        }

        /// <summary>
        /// Show Frames in Overview Columns
        /// </summary>
        /// <param name="sender">Message Sender</param>
        /// <param name="e">Event Arguments</param>
        private void FramesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            TotalFrames.Visible = totalsToolStripMenuItem.Checked & framesToolStripMenuItem.Checked & rawDataToolStripMenuItem.Checked;
            TotalFramesPercent.Visible = totalsToolStripMenuItem.Checked & framesToolStripMenuItem.Checked & percentagesToolStripMenuItem.Checked;
            Frames.Visible = framesToolStripMenuItem.Checked & rawDataToolStripMenuItem.Checked;
            FramesPercent.Visible = framesToolStripMenuItem.Checked & percentagesToolStripMenuItem.Checked;
            ReassembledFrames.Visible = framesToolStripMenuItem.Checked & rawDataToolStripMenuItem.Checked & reassembleProtocolsToolStripMenuItem.Checked;
            ReassembledFramesPercent.Visible = framesToolStripMenuItem.Checked & percentagesToolStripMenuItem.Checked & reassembleProtocolsToolStripMenuItem.Checked;
        }

        /// <summary>
        /// Show Percentages in Overview Columns
        /// </summary>
        /// <param name="sender">Message Sender</param>
        /// <param name="e">Event Arguments</param>
        private void PercentagesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            TotalBytesPercent.Visible = totalsToolStripMenuItem.Checked & bytesToolStripMenuItem.Checked & percentagesToolStripMenuItem.Checked;
            TotalFramesPercent.Visible = totalsToolStripMenuItem.Checked & framesToolStripMenuItem.Checked & percentagesToolStripMenuItem.Checked;
            BytesPercent.Visible = bytesToolStripMenuItem.Checked & percentagesToolStripMenuItem.Checked;            
            FramesPercent.Visible = framesToolStripMenuItem.Checked & percentagesToolStripMenuItem.Checked;
            ReassembledBytesPercent.Visible = bytesToolStripMenuItem.Checked & percentagesToolStripMenuItem.Checked & reassembleProtocolsToolStripMenuItem.Checked;
            ReassembledFramesPercent.Visible = framesToolStripMenuItem.Checked & percentagesToolStripMenuItem.Checked & reassembleProtocolsToolStripMenuItem.Checked;
        }

        /// <summary>
        /// Show Raw Data in Overview Columns
        /// </summary>
        /// <param name="sender">Message Sender</param>
        /// <param name="e">Event Arguments</param>
        private void RawDataToolStripMenuItem_Click(object sender, EventArgs e)
        {
            TotalBytes.Visible = totalsToolStripMenuItem.Checked & bytesToolStripMenuItem.Checked & rawDataToolStripMenuItem.Checked;
            TotalFrames.Visible = totalsToolStripMenuItem.Checked & framesToolStripMenuItem.Checked & rawDataToolStripMenuItem.Checked;
            Bytes.Visible = bytesToolStripMenuItem.Checked & rawDataToolStripMenuItem.Checked;
            Frames.Visible = framesToolStripMenuItem.Checked & rawDataToolStripMenuItem.Checked;
            ReassembledBytes.Visible = bytesToolStripMenuItem.Checked & rawDataToolStripMenuItem.Checked & reassembleProtocolsToolStripMenuItem.Checked;
            ReassembledFrames.Visible = framesToolStripMenuItem.Checked & rawDataToolStripMenuItem.Checked & reassembleProtocolsToolStripMenuItem.Checked;
        }

        /// <summary>
        /// Reassemble Protocols
        /// </summary>
        /// <param name="sender">Message Sender</param>
        /// <param name="e">Event Arguments</param>
        private void ReassembleProtocolsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            ReassembledBytes.Visible = bytesToolStripMenuItem.Checked & rawDataToolStripMenuItem.Checked & reassembleProtocolsToolStripMenuItem.Checked;
            ReassembledBytesPercent.Visible = bytesToolStripMenuItem.Checked & percentagesToolStripMenuItem.Checked & reassembleProtocolsToolStripMenuItem.Checked;
            ReassembledFrames.Visible = framesToolStripMenuItem.Checked & rawDataToolStripMenuItem.Checked & reassembleProtocolsToolStripMenuItem.Checked;
            ReassembledFramesPercent.Visible = framesToolStripMenuItem.Checked & percentagesToolStripMenuItem.Checked & reassembleProtocolsToolStripMenuItem.Checked;
        }
        #endregion
        
        #region Overview Menu Controls
        /// <summary>
        /// Selects the item in the Time Graph
        /// </summary>
        /// <param name="sender">Message Sender</param>
        /// <param name="e">Event Arguments</param>
        private void SelectInTimeGraphToolStripMenuItem_Click(object sender, EventArgs e)
        {
            tabControl.SelectTab(tabPageTimeGraph);

            TreeNode s = this.BranchSelect(TimeTree.Nodes, (uint)TPTree.SelectedNode.Tag);
            if (s != null)
            {
                TimeTree.SelectedNode = s;
            }
        }

        /// <summary>
        /// Selects all the filtered items in the Time Graph
        /// </summary>
        /// <param name="sender">Message Sender</param>
        /// <param name="e">Event Arguments</param>
        private void ShowFilteredOnTimeGraphToolStripMenuItem_Click(object sender, EventArgs e)
        {
            tabControl.SelectTab(tabPageTimeGraph);

            TreeNode s = this.BranchSelect(TimeTree.Nodes, (uint)TPTree.SelectedNode.Tag);
            if (s != null)
            {
                this.CheckChildren(TimeTree.Nodes, false);

                TimeTree.SelectedNode = s;

                s.Checked = true;
            }
        }

        /// <summary>
        /// Removes the Filter on the Grid based on the Tree Selection
        /// </summary>
        /// <param name="sender">Message Sender</param>
        /// <param name="e">Event Arguments</param>
        private void RemoveFilterToolStripMenuItem_Click(object sender, EventArgs e)
        {
            foreach (DataGridViewRow row in TUGrid.Rows)
            {
                row.Visible = true;
            }
        }

        /// <summary>
        /// Resorts the Grid to restore the initial view
        /// </summary>
        /// <param name="sender">Message Sender</param>
        /// <param name="e">Event Arguments</param>
        private void RestoreDefaultOrderingToolStripMenuItem_Click(object sender, EventArgs e)
        {
            TUGrid.Sort(Order, ListSortDirection.Ascending);
        }
        #endregion

        #region Overview Controls
        /// <summary>
        /// Provides the ability to click in the TreeView after the node was 
        /// already selected by the grid
        /// </summary>
        /// <param name="sender">Message Sender</param>
        /// <param name="e">Event Arguments</param>
        private void TPTree_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
        {
            TreeViewCancelEventArgs args = new TreeViewCancelEventArgs(e.Node, false, new TreeViewAction());
            this.TPTree_BeforeSelect(sender, args);

            if (e.Button == MouseButtons.Right)
            {
                TPTree.SelectedNode = e.Node;
            }
        }

        /// <summary>
        /// Selects the Grid Row based on the Tree Selection, if the Filter option is on
        /// will also hide grid rows which don't display information about any child nodes
        /// for the node selected.
        /// </summary>
        /// <param name="sender">Message Sender</param>
        /// <param name="e">Event Arguments</param>
        private void TPTree_BeforeSelect(object sender, TreeViewCancelEventArgs e)
        {
            // Prevent infinite loop
            if (this.selecting)
            {
                return;
            }

            this.selecting = true;

            TUGrid.ClearSelection();

            List<uint> subtags = this.GetAllSubTags(e.Node);

            foreach (DataGridViewRow row in TUGrid.Rows)
            {
                uint val = (uint)row.Cells[14].Value; // Grab Order Column value

                // Set row visibility based on options
                row.Visible = !filterGridOnTreeSelectionToolStripMenuItem.Checked || (filterGridOnTreeSelectionToolStripMenuItem.Checked && subtags.Contains(val));

                // Use Order (unique ID) to get correct Row
                if (val == (uint)e.Node.Tag)
                {
                    for (int x = Math.Max(row.Index - 8, 0); x <= row.Index; x++)
                    {
                        if (TUGrid.Rows[x].Visible == true)
                        {
                            TUGrid.FirstDisplayedScrollingRowIndex = x;
                            break;
                        }
                    }

                    row.Cells[0].Selected = true;
                }
            }

            TUGrid.Refresh();

            this.selecting = false;
        }

        /// <summary>
        /// Used to Highlight Row when Right-Clicking
        /// </summary>
        /// <param name="sender">Message Sender</param>
        /// <param name="e">Event Arguments</param>
        private void TUGrid_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Right)
            {
                TUGrid.ClearSelection();
                DataGridView.HitTestInfo hti = TUGrid.HitTest(e.X, e.Y);
                if (hti.Type == DataGridViewHitTestType.Cell || hti.Type == DataGridViewHitTestType.RowHeader)
                {
                    TUGrid.Rows[hti.RowIndex].Selected = true;
                    TUGrid.Refresh();
                    this.TUGrid_SelectionChanged(sender, null);
                }
            }
        }

        /// <summary>
        /// Selects the Tree Node based on the Row Selection
        /// </summary>
        /// <param name="sender">Message Sender</param>
        /// <param name="e">Event Arguments</param>
        private void TUGrid_SelectionChanged(object sender, EventArgs e)
        {
            // Prevent infinite loop
            if (this.selecting)
            {
                return;
            }

            if (TUGrid.SelectedRows.Count > 0)
            {
                this.selecting = true;

                TPTree.SelectedNode = this.BranchSelect(TPTree.Nodes, (uint)TUGrid.Rows[TUGrid.SelectedRows[0].Index].Cells[14].Value);

                TPTree.Refresh();

                this.selecting = false;
            }
        }

        /// <summary>
        /// Used to Copy The Grid to the Clipboard for pasting in Excel
        /// </summary>
        /// <param name="sender">Message sender</param>
        /// <param name="e">Event Arguments</param>
        private void CopyAsTabSeparatedToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Copy Column Headers
            string text = string.Empty;
            foreach (DataGridViewColumn col in this.TUGrid.Columns)
            {
                if (col.Visible)
                {
                    text += col.HeaderText + "\t";
                }
            }

            text += "\n";

            // Copy All Rows
            foreach (DataGridViewRow row in this.TUGrid.Rows)
            {
                foreach (DataGridViewCell cell in row.Cells)
                {
                    if (cell.Visible)
                    {
                        text += cell.Value.ToString() + "\t";
                    }
                }

                text += "\n";
            }

            Clipboard.SetText(text);
        }

        /// <summary>
        /// Used to select the specific Node based on the tag ID
        /// </summary>
        /// <param name="nodes">Collection of Nodes</param>
        /// <param name="tag">Protocol ID</param>
        /// <returns>Resulting Node</returns>
        private TreeNode BranchSelect(TreeNodeCollection nodes, uint tag)
        {
            foreach (TreeNode n in nodes)
            {
                if (tag == (uint)n.Tag)
                {
                    return n;
                }
                else if (n.Nodes.Count > 0)
                {
                    TreeNode r = this.BranchSelect(n.Nodes, tag);
                    if (r != null)
                    {
                        return r;
                    }
                }
            }

            return null;
        }

        /// <summary>
        /// Used to get a list of Tag IDs for all the nodes under and including the 
        /// passed in node
        /// </summary>
        /// <param name="node">Tree Node to Expand</param>
        /// <returns>List of Tag IDs</returns>
        private List<uint> GetAllSubTags(TreeNode node)
        {
            List<uint> list = new List<uint>();

            this._getallsubtags(node, ref list);

            return list;
        }

        /// <summary>
        /// Private function used by GetAllSubTags
        /// </summary>
        /// <param name="pnode">Current Node</param>
        /// <param name="list">Current List</param>
        private void _getallsubtags(TreeNode pnode, ref List<uint> list)
        {
            list.Add((uint)pnode.Tag);

            foreach (TreeNode node in pnode.Nodes)
            {
                this._getallsubtags(node, ref list);
            }
        }

        #endregion

        #region Time Graph Controls
        /// <summary>
        /// Used to check the children of the checked node
        /// </summary>
        /// <param name="sender">Message Sender</param>
        /// <param name="e">Event Arguments</param>
        private void TimeTree_BeforeCheck(object sender, TreeViewCancelEventArgs e)
        {
            this.CheckChildren(e.Node.Nodes, !e.Node.Checked);

            foreach (Series s in this.timeSeriesChartControl.Series)
            {
                if (s.Name == e.Node.Tag.ToString())
                {
                    if (s.Enabled != !e.Node.Checked)
                    {
                        s.Enabled = !e.Node.Checked;
                    }

                    break;
                }
            }
        }

        /// <summary>
        /// Called by TimeTree_BeforeCheck to check Children of the Node
        /// </summary>
        /// <param name="nodes">Nodes to Check</param>
        /// <param name="value">Value to Set</param>
        private void CheckChildren(TreeNodeCollection nodes, bool value)
        {
            foreach (TreeNode tn in nodes)
            {
                this.CheckChildren(tn.Nodes, value);

                if (tn.Checked != value)
                {
                    tn.Checked = value;
                }
            }
        }

        /// <summary>
        /// Used to Highlight the selected Node in the Graph
        /// </summary>
        /// <param name="sender">Message Sender</param>
        /// <param name="e">Event Arguments</param>
        private void TimeTree_AfterSelect(object sender, TreeViewEventArgs e)
        {
            // Make Selected Node's line larger
            foreach (Series ser in this.timeSeriesChartControl.Series)
            {
                if (uint.Parse(ser.Name, CultureInfo.InvariantCulture) == (uint)e.Node.Tag)
                {
                    ser.BorderWidth = 3;
                    this.selectedSeries = ser;
                }
                else
                {
                    ser.BorderWidth = 1;
                }
            }
        }

        /// <summary>
        /// Selects the item in the Overview Tree
        /// </summary>
        /// <param name="sender">Message Sender</param>
        /// <param name="e">Event Arguments</param>
        private void SelectInOverviewTGToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (TimeTree.SelectedNode != null)
            {
                tabControl.SelectTab(tabPageOverview);

                TPTree.SelectedNode = this.BranchSelect(TPTree.Nodes, (uint)TimeTree.SelectedNode.Tag);

                TPTree.Refresh();
            }
        }
        
        /// <summary>
        /// Allows Mouse Wheel to control Y-axis zoom level
        /// </summary>
        /// <param name="sender">Message Sender</param>
        /// <param name="e">Event Arguments</param>
        private void OnMouseWheel(object sender, MouseEventArgs e)
        {
            if (tabControl.SelectedTab == tabPageTimeGraph)
            {
                if (e.Delta < 0)
                {
                    this.timeSeriesChartControl.ChartAreas[0].AxisY.Maximum = this.timeSeriesChartControl.ChartAreas[0].AxisY.Maximum * 2;
                }
                else
                {
                    this.timeSeriesChartControl.ChartAreas[0].AxisY.Maximum = this.timeSeriesChartControl.ChartAreas[0].AxisY.Maximum / 2;
                }

                this.timeSeriesChartControl.Refresh();
            }
        }

        /// <summary>
        /// Node Selection in Tree in Time Graph
        /// </summary>
        /// <param name="sender">Message Sender</param>
        /// <param name="e">Event Arguments</param>
        private void TimeTree_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
        {
            if (e.Button == MouseButtons.Right)
            {
                TimeTree.SelectedNode = e.Node;

                this.selectedPoint = null;
            }
        }

        /// <summary>
        /// Rescales the Time Graph based on the current selection
        /// </summary>
        /// <param name="sender">Message Sender</param>
        /// <param name="e">Event Arguments</param>
        private void TimeTree_AfterCheck(object sender, TreeViewEventArgs e)
        {
            // TODO: Make sure this is only called after all selections (in automated cases) have stabilized
            this.TimeGraphRescale();

            this.timeSeriesChartControl.Refresh();
        }

        /// <summary>
        /// Rescales the time axis of the Graph to fit only the visible data
        /// </summary>
        private void TimeGraphRescale()
        {
            if (automaticallyScaleTimeAxisToolStripMenuItem.Checked)
            {
                double minx = double.MaxValue;
                double maxx = 0;
                double maxy = 0;

                int seriesEnabled = 0;

                // Make Selected Node's line larger
                foreach (Series ser in this.timeSeriesChartControl.Series)
                {
                    // Find Boundries
                    if (ser.Enabled)
                    {
                        seriesEnabled++;

                        double lmin = double.MaxValue;
                        double lmax = 0;
                        for (int x = 1; x < ser.Points.Count; x++)
                        {
                            double yval = ser.Points[x].YValues[0];
                            if (yval > 0 && ser.Points[x].XValue < lmin)
                            {
                                // Grab previous point for boundry to keep with Graph meaning
                                lmin = ser.Points[x - 1].XValue;
                            }

                            if (yval > 0 && ser.Points[x].XValue > lmax)
                            {
                                lmax = ser.Points[x].XValue;
                            }

                            if (yval > maxy)
                            {
                                maxy = yval;
                            }
                        }

                        if (lmin < minx)
                        {
                            minx = lmin;
                        }

                        if (lmax > maxx)
                        {
                            maxx = lmax;
                        }
                    }
                }

                // Was getting series with all zero Y values
                // Was extra remainder data in frames...
                if (seriesEnabled > 0 && maxy > 0)
                {
                    this.timeSeriesChartControl.ChartAreas[0].AxisX.Minimum = minx;
                    this.timeSeriesChartControl.ChartAreas[0].AxisX.Maximum = maxx;
                    this.timeSeriesChartControl.ChartAreas[0].AxisY.Maximum = maxy;
                }
            }
        }

        /// <summary>
        /// Used to calculate the closest line/point on the Time Graph
        /// </summary>
        /// <param name="sender">Message Sender</param>
        /// <param name="e">Event Arguments</param>
        private void TimeSeriesChartControl_MouseMove(object sender, MouseEventArgs e)
        {
            // Find closest point...
            this.mousePoint = new Point(e.X, e.Y);
            this.timeSeriesChartControl.Refresh();

            if (this.chartX > 0 && this.chartY > 0)
            {
                uint d = uint.MaxValue;
                uint gap = (uint)(this.timeSeriesChartControl.ChartAreas[0].AxisY.Maximum / (double)this.numberOfSlices);
                this.chartX *= gap;

                foreach (Series ser in this.timeSeriesChartControl.Series)
                {
                    if (ser.Enabled)
                    {
                        foreach (DataPoint p in ser.Points)
                        {
                            p.Label = string.Empty;
                            p.MarkerBorderWidth = 0;

                            uint pd = (uint)Math.Sqrt(Math.Pow((p.XValue * gap) - this.chartX, 2.0) + Math.Pow(p.YValues[0] - this.chartY, 2.0));
                            if (pd < d)
                            {
                                d = pd;
                                this.selectedPoint = p;
                                this.selectedSeries = ser;
                            }
                        }
                    }
                }

                if (this.selectedPoint != null)
                {
                    this.selectedPoint.Label = this.selectedSeries.Tag.ToString() + ": " + Math.Round(this.selectedPoint.XValue, 2) + ", " + this.selectedPoint.YValues[0];
                    this.selectedPoint.MarkerBorderWidth = 3;
                }

                if (this.selectedSeries != null)
                {
                    TimeTree.SelectedNode = this.BranchSelect(TimeTree.Nodes, uint.Parse(this.selectedSeries.Name, CultureInfo.InvariantCulture));
                }
            }
        }

        /// <summary>
        /// Used to track where the mouse is, only valid in Paint call and not MouseMove.
        /// Used in combination with timeSeriesChartControl_MouseMove.
        /// </summary>
        /// <param name="sender">Message Sender</param>
        /// <param name="e">Event Arguments</param>
        private void TimeSeriesChartControl_Paint(object sender, PaintEventArgs e)
        {
            try
            {
                this.chartX = this.timeSeriesChartControl.ChartAreas[0].AxisX.PixelPositionToValue(this.mousePoint.X);
                this.chartY = this.timeSeriesChartControl.ChartAreas[0].AxisY.PixelPositionToValue(this.mousePoint.Y);
            }
            catch (ArgumentException)
            {
                // Happens when resizing smaller as last mouse point is now outside range of chart
            }
        }

        /// <summary>
        /// Hides all other graph items except children
        /// </summary>
        /// <param name="sender">Message Sender</param>
        /// <param name="e">Event Arguments</param>
        private void ShowOnlyThisToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (TimeTree.SelectedNode != null)
            {
                this.CheckChildren(this.TimeTree.Nodes, false);

                TimeTree.SelectedNode.Checked = true;
            }
        }

        /// <summary>
        /// Hides the selected item from view
        /// </summary>
        /// <param name="sender">Message Sender</param>
        /// <param name="e">Event Arguments</param>
        private void HideThisToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (this.selectedSeries != null)
            {
                this.selectedSeries.Enabled = false;

                this.BranchSelect(TimeTree.Nodes, uint.Parse(this.selectedSeries.Name, CultureInfo.InvariantCulture)).Checked = false;
            }
        }

        /// <summary>
        /// Sets the Y-Axis Maximum to the selected points value
        /// </summary>
        /// <param name="sender">Message Sender</param>
        /// <param name="e">Event Arguments</param>
        private void SetYMaximumHereToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (this.selectedSeries != null)
            {
                if (this.selectedPoint == null || (this.selectedPoint.XValue == 0 && this.selectedPoint.YValues[0] == 0))
                {
                    double max = 0;
                    foreach (Series ser in this.timeSeriesChartControl.Series)
                    {
                        if (uint.Parse(ser.Name, CultureInfo.InvariantCulture) == (uint)TimeTree.SelectedNode.Tag)
                        {
                            foreach (DataPoint p in ser.Points)
                            {
                                if (p.YValues[0] > max)
                                {
                                    this.selectedPoint = p;
                                    max = p.YValues[0];
                                }
                            }

                            break;
                        }
                    }
                }

                if (this.selectedPoint.YValues[0] > 0)
                {
                    this.timeSeriesChartControl.ChartAreas[0].AxisY.Maximum = this.selectedPoint.YValues[0];
                }
            }
        }
        #endregion

        #region Advanced Options Logic
        /// <summary>
        /// Will Remove any Filter when option is disabled
        /// </summary>
        /// <param name="sender">Message Sender</param>
        /// <param name="e">Event Arguments</param>
        private void FilterGridOnTreeSelectionToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (!filterGridOnTreeSelectionToolStripMenuItem.Checked)
            {
                this.RemoveFilterToolStripMenuItem_Click(null, null);
            }
        }

        /// <summary>
        /// Removes Tab Page and Disables Time Graph Menu Items when Option is Disabled
        /// </summary>
        /// <param name="sender">Message Sender</param>
        /// <param name="e">Event Arguments</param>
        private void CalculateTimeGraphToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (!calculateTimeGraphToolStripMenuItem.Checked)
            {
                tabControl.TabPages.Remove(tabPageTimeGraph);
            }
            else
            {
                tabControl.TabPages.Insert(this.numberOfTabs - 2, tabPageTimeGraph);
            }

            selectInTimeGraphToolStripMenuItem.Enabled = calculateTimeGraphToolStripMenuItem.Checked;
            showFilteredOnTimeGraphToolStripMenuItem.Enabled = calculateTimeGraphToolStripMenuItem.Checked;
        }

        /// <summary>
        /// Saves the Current Options
        /// </summary>
        /// <param name="sender">Message Sender</param>
        /// <param name="e">Event Arguments</param>
        private void SaveOptionsNowToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.SaveOptions();
        }
        #endregion
    }
}

